/*
 * Copyright (c) 2009 Charles S. Wilson
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a 
 * copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation 
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the 
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included 
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include <stdarg.h>
#include <libxml/globals.h>
#include <libxml/xmlerror.h>
#include <libxml/xmlmemory.h>
#include <libxml/relaxng.h>
#include <libxml/xmlschemas.h>

#include "confparser.h"
#include "confparse.h"
#include "confdata.h"
#include "util.h"

static const char * g_xmlfilename;

static void
xmlSchemaValidityError (void * ctx, const char * msg, ...)
{
  va_list args;
  if (!g_xmlfilename || !*g_xmlfilename)
  {
    char *newmsg = run2_extend_str (msg, "Errors in XML file: ", 0);
    va_start (args, msg);
    verrorMsg (newmsg, args);
    va_end (args);
    free (newmsg);
  }
  else
  {
    char *newmsg1;
    char *newmsg2;
    newmsg1 = run2_extend_str("Errors in XML file '", g_xmlfilename, 1);
    newmsg2 = run2_extend_str(newmsg1, "': ", 1);
    free (newmsg1);
    newmsg1 = run2_extend_str(msg, newmsg2, 0);
    free (newmsg2);
    va_start (args, msg);
    verrorMsg (newmsg1, args);
    va_end (args);
    free (newmsg1);
  }
  exit (1);
}

static void
xmlSchemaValidityWarning (void * ctx, const char * msg, ...)
{
  va_list args;
  va_start (args, msg);
  vwarn (msg, args);
  va_end (args);
}

run2_xml_parser_t *
run2_xml_parser_create (void)
{
  xmlSAXHandler * handler = NULL;
  xmlSchemaPtr schemaPtr = NULL;
  xmlSchemaValidCtxtPtr validatorCtxt = NULL;
  run2_xml_parser_t * parser = NULL;

  LIBXML_TEST_VERSION

  parser = (run2_xml_parser_t *) run2_malloc (sizeof (run2_xml_parser_t));
  memset (parser, 0, sizeof(run2_xml_parser_t));

  handler = (xmlSAXHandler *) run2_malloc (sizeof (xmlSAXHandler));
  memset (handler, 0, sizeof (xmlSAXHandler));
  handler->startDocument  = &run2_xml_startdocument;
  handler->endDocument    = &run2_xml_enddocument;
  handler->characters     = &run2_xml_characters;
  handler->initialized    = XML_SAX2_MAGIC; /* force parsing as SAX2 */
  handler->startElementNs = &run2_xml_startelementns;
  handler->endElementNs   = &run2_xml_endelementns;

  {
    /* Load schema */
    xmlSchemaParserCtxtPtr schemaParserCtxt =
        xmlSchemaNewMemParserCtxt(RUN2_XSD, strlen(RUN2_XSD));
    xmlSchemaSetParserErrors(schemaParserCtxt,
      &xmlSchemaValidityError,
      &xmlSchemaValidityWarning,
      NULL);
    schemaPtr = xmlSchemaParse(schemaParserCtxt);
    xmlSchemaFreeParserCtxt(schemaParserCtxt);
    if (schemaPtr == NULL)
    {
      xmlGenericError(xmlGenericErrorContext,
        "Internal error: unable to compile builtin schema\n");
      goto cleanup;
    }
  }

  validatorCtxt = xmlSchemaNewValidCtxt(schemaPtr);
  if (validatorCtxt == NULL)
  {
    xmlGenericError(xmlGenericErrorContext,
      "Internal error: unable to construct validator from schema\n");
    goto cleanup;
  }
  xmlSchemaSetValidErrors(validatorCtxt,
      &xmlSchemaValidityError,
      &xmlSchemaValidityWarning,
      NULL);

  parser->handler = (void *) handler;
  parser->schema  = (void *) schemaPtr;
  parser->validator = (void *) validatorCtxt;
  return parser;

cleanup:
  if (handler)
    free (handler);
  if (schemaPtr)
    xmlSchemaFree(schemaPtr);
  if (validatorCtxt)
    xmlSchemaFreeValidCtxt(validatorCtxt);
  if (parser)
    free (parser);
  return NULL;
}

int
run2_xml_parser_parse (run2_xml_parser_t * parser)
{
  xmlSAXHandler * handler = NULL;
  xmlSchemaPtr schemaPtr = NULL;
  xmlSchemaValidCtxtPtr validatorCtxt = NULL;
  xmlParserInputBufferPtr buf = NULL;
  g_xmlfilename = NULL;
  conf_parser_state_t parser_state;
  int res;

  if (!parser)
  {
    errorMsg ("run2_xml_parser_parse called with <NULL> argument");
    return 1;
  }
  if (!parser->handler || !parser->schema || !parser->validator)
  {
    errorMsg ("run2_xml_parser_parse called with improper context");
    return 1;
  }
  if (parser->confdata)
  {
    errorMsg ("run2_xml_parser_parse context already "
              "contains configuration data");
    return 1;
  }
  if (!parser->xmlfilename || !*parser->xmlfilename)
  {
    errorMsg ("run2_xml_parser_parse called with no filename to parse");
    return 1;
  }
  schemaPtr = (xmlSchemaPtr) parser->schema;
  handler = (xmlSAXHandler *) parser->handler;
  validatorCtxt = (xmlSchemaValidCtxtPtr) parser->validator;
  g_xmlfilename = parser->xmlfilename;

  memset(&parser_state, 0, sizeof(conf_parser_state_t));
  buf = xmlParserInputBufferCreateFilename(parser->xmlfilename,
                                           XML_CHAR_ENCODING_NONE);
  if (buf == NULL) {
    fatalMsg ("Unable to read %s", parser->xmlfilename);
    return 1;
  }

  /* validate and parse */
  res = xmlSchemaValidateStream(
      validatorCtxt, buf, 0, handler, (void*)&parser_state);

  if (res == 0)
  {
    infoMsg ("%s validated", g_xmlfilename);
    parser->confdata =  parser_state.data;
  }
  else if (res > 0)
  {
    fatalMsg ("%s failed to validate", g_xmlfilename);
  }
  else
  {
    fatalMsg ("%s validation generated an internal error", g_xmlfilename);
  }
  g_xmlfilename = NULL;
  return res;
}

void
run2_xml_parser_destroy (run2_xml_parser_t * parser)
{
  if (!parser)
  {
    return;
  }
  if (parser->handler)
  {
    free (parser->handler);
    parser->handler = NULL;
  }
  if (parser->schema)
  {
    xmlSchemaFree ((xmlSchemaPtr)parser->schema);
    parser->schema = NULL;
  }
  if (parser->validator)
  {
    xmlSchemaFreeValidCtxt ((xmlSchemaValidCtxtPtr)parser->validator);
    parser->validator = NULL;
  }
  if (parser->confdata)
  {
    run2_confdata_delete (parser->confdata);
    parser->confdata = NULL;
  }
  if (parser->xmlfilename)
  {
    free ((void *)parser->xmlfilename);
    parser->xmlfilename = NULL;
  }
}

run2_confdata_t *
run2_xml_parse (const char * xmlfilename)
{
  run2_xml_parser_t *parser = NULL;
  run2_confdata_t   *data = NULL;

  if (!xmlfilename || !*xmlfilename)
  {
    errorMsg ("XML file not specified");
    goto cleanup;
  }

  if ((parser = run2_xml_parser_create ()) == NULL)
  {
    errorMsg ("Failed to construct xml parser");
    goto cleanup;
  }
  parser->xmlfilename = run2_strdup (xmlfilename);
  if (run2_xml_parser_parse (parser) != 0)
  {
    errorMsg ("There was an error parsing '%s'", xmlfilename);
    goto cleanup;
  }
  data = parser->confdata;
  /* Ensure that destroying the parser doesn't destroy the data */
  parser->confdata = NULL;

cleanup:
  if (parser)
  {
    run2_xml_parser_destroy (parser);
    parser = NULL;
  }
  return data;
}

